/*-------------------------------------------------------------------------
 *
 * crashdump.c
 *		   为 PostgreSQL 在 Windows 上自动创建崩溃转储
 *
 * crashdump 功能捕获后台产生的未处理的 win32 异常，并尝试生成
 * Windows MiniDump 崩溃转储以供日后调试和分析。执行转储的机器不需要
 * 任何特殊的调试工具；用户只需将转储发送给拥有相同版本 PostgreSQL
 * 和调试工具的人。
 *
 * crashdump 模块最初由 Craig Ringer <ringerc@ringerc.id.au> 开发
 *
 * 限制
 * ===========
 * 在严重的 OOM 情况或堆栈溢出时，这 *不会* 工作。
 *
 * 在这些情况下，必须采用更复杂的方法，其中处理程序切换到新的堆栈
 * （如果可以的话）并分叉一个辅助进程来进行自我调试。
 *
 * 未来可能的工作
 * ====================
 * 为了积分奖励，崩溃转储格式允许嵌入用户提供的数据。如果还有其他任何
 * 内容在崩溃转储时总是需要提供（postgresql.conf？日志文件的最后几行？），
 * 它可能被添加，但代价是崩溃转储失败的几率更大。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/port/win32/crashdump.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

/*
 * 一些版本的MS SDK包含“typedef enum { ... } ;”，这让MS编译器相当合理地提出了警告。干得好，微软。
 * 这个pragma在我们包含头文件时禁用警告。
 * 已知该pragma与所有（截至撰写时）支持的MSVC版本兼容。
 */
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4091)
#endif
#include <dbghelp.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif

/*
 * 以下大部分代码基于CodeProject和MSDN示例，
 * 特别是
 * http://www.codeproject.com/KB/debug/postmortemdebug_standalone1.aspx
 *
 * 有用的MSDN文章：
 *
 * http://msdn.microsoft.com/en-us/library/ff805116(v=VS.85).aspx
 * http://msdn.microsoft.com/en-us/library/ms679294(VS.85).aspx
 *
 * 关于处理minidumps的其他有用文章：
 * http://www.debuginfo.com/articles/effminidumps.html
 */

typedef BOOL (WINAPI * MINIDUMPWRITEDUMP) (HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType,
										   CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
										   CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
										   CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam
);


/*
 * 该函数是传递给SetUnhandledExceptionFilter的异常处理程序。
 * 仅在发生未处理异常时调用。处理程序将使用
 * dbghelp.dll生成崩溃转储，然后恢复正常的未处理
 * 异常过程，通常将以运行时的错误消息退出。
 *
 * 此函数在未处理异常处理程序下运行，实际上是在崩溃上下文中，
 * 因此应谨慎处理内存，避免使用任何PostgreSQL函数。
 */
static LONG WINAPI
crashDumpHandler(struct _EXCEPTION_POINTERS *pExceptionInfo)
{
	/*
	 * 只有在postgres数据目录中的“crashdumps”目录存在时，我们才会写入崩溃转储。
	 */
	DWORD		attribs = GetFileAttributesA("crashdumps");

	if (attribs != INVALID_FILE_ATTRIBUTES && (attribs & FILE_ATTRIBUTE_DIRECTORY))
	{
		/* 'crashdumps'存在且是一个目录。尝试写入转储' */
		HMODULE		hDll = NULL;
		MINIDUMPWRITEDUMP pDump = NULL;
		MINIDUMP_TYPE dumpType;
		char		dumpPath[_MAX_PATH];
		HANDLE		selfProcHandle = GetCurrentProcess();
		DWORD		selfPid = GetProcessId(selfProcHandle);
		HANDLE		dumpFile;
		DWORD		systemTicks;
		struct _MINIDUMP_EXCEPTION_INFORMATION ExInfo;

		ExInfo.ThreadId = GetCurrentThreadId();
		ExInfo.ExceptionPointers = pExceptionInfo;
		ExInfo.ClientPointers = FALSE;

		/* 加载dbghelp.dll库和函数 */
		hDll = LoadLibrary("dbghelp.dll");
		if (hDll == NULL)
		{
			write_stderr("could not load dbghelp.dll, cannot write crash dump\n");
			return EXCEPTION_CONTINUE_SEARCH;
		}

		pDump = (MINIDUMPWRITEDUMP) (pg_funcptr_t) GetProcAddress(hDll, "MiniDumpWriteDump");

		if (pDump == NULL)
		{
			write_stderr("could not load required functions in dbghelp.dll, cannot write crash dump\n");
			return EXCEPTION_CONTINUE_SEARCH;
		}

		/*
		 * 尽可能多地转储，除了共享内存、代码段和
		 * 内存映射文件。我们能够转储的内容具体取决于
		 * dbghelp.dll的版本，详见：
		 * http://msdn.microsoft.com/en-us/library/ms680519(v=VS.85).aspx
		 */
		dumpType = MiniDumpNormal | MiniDumpWithHandleData |
			MiniDumpWithDataSegs;

		if (GetProcAddress(hDll, "EnumDirTree") != NULL)
		{
			/* 如果该函数存在，我们有版本5.2或更新版本 */
			dumpType |= MiniDumpWithIndirectlyReferencedMemory |
				MiniDumpWithPrivateReadWriteMemory;
		}

		systemTicks = GetTickCount();
		snprintf(dumpPath, _MAX_PATH,
				 "crashdumps\\postgres-pid%0i-%0i.mdmp",
				 (int) selfPid, (int) systemTicks);
		dumpPath[_MAX_PATH - 1] = '\0';

		dumpFile = CreateFile(dumpPath, GENERIC_WRITE, FILE_SHARE_WRITE,
							  NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
							  NULL);
		if (dumpFile == INVALID_HANDLE_VALUE)
		{
			write_stderr("could not open crash dump file \"%s\" for writing: error code %lu\n",
						 dumpPath, GetLastError());
			return EXCEPTION_CONTINUE_SEARCH;
		}

		if ((*pDump) (selfProcHandle, selfPid, dumpFile, dumpType, &ExInfo,
					  NULL, NULL))
			write_stderr("wrote crash dump to file \"%s\"\n", dumpPath);
		else
			write_stderr("could not write crash dump to file \"%s\": error code %lu\n",
						 dumpPath, GetLastError());

		CloseHandle(dumpFile);
	}

	return EXCEPTION_CONTINUE_SEARCH;
}


void pgwin32_install_crashdump_handler(void)
{
	SetUnhandledExceptionFilter(crashDumpHandler);
}
